<?php 
class ModelShippingWeight3d extends Model {
	public function getQuote($address) {
		$this->load->language('shipping/weight3d');
		include_once(DIR_SYSTEM . 'config/config.php');

		$quote_data = array();
		if ($this->config->get('weight3d_status')) {
			$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "geo_zone ORDER BY name");
			foreach ($query->rows as $result) {
				$geo_zone_id = (int) $result['geo_zone_id'];
				$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . $geo_zone_id . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");
				// for all available zones
				foreach ($query->rows as $result1) {
					$service_classes_enabled = array();
					// if shipping is enabled to zone
					for ($i=0; $i<MAX_SHIPPING_SERVICE_CLASSES; $i++) {
						if ($this->config->get('weight3d_' . $geo_zone_id . '_' . $i . '_status')) {
							$service_classes_enabled[] = $i;
						}
					}
					if (count($service_classes_enabled) > 0) {
						$allShippableItemsHaveDimensionAndWeight = true;
						// prepare cart items for calculator
						$items = array();
						$weight_converter = $this->registry->get('weight');
						$length_converter = $this->registry->get('length');
						foreach ($this->cart->getProducts() as $key=>$attrs) {
							// if product requires shipping
							if ($attrs['shipping']) {
								$product_id = trim($attrs['product_id']);
								$quantity = trim($attrs['quantity']);
								// get item dimension and weight
								$length = trim($attrs['length']);
								$width = trim($attrs['width']);
								$height = trim($attrs['height']);
								$weight = trim($attrs['weight']);
								// if dimension and weight are defined
								if (is_numeric($length) && $length > 0 && is_numeric($width) && $width > 0 && is_numeric($height) && $height > 0 && is_numeric($weight) && $weight > 0) {
									// convert weight to default unit
									$weight_class = $attrs['weight_class'];
									$weight = $weight_converter->convert($weight, $weight_class, $this->config->get('config_weight_class'));
									// convert length to default unit
									$length_class = $attrs['length_class'];
									$length = $length_converter->convert($length, $length_class, $this->config->get('config_length_class'));
									// convert width to default unit
									$width = $length_converter->convert($width, $length_class, $this->config->get('config_length_class'));
									// convert height to default unit
									$height = $length_converter->convert($height, $length_class, $this->config->get('config_length_class'));
									// add item to be calculated
									$items[] = array(substr($product_id, 0, 60), $weight, $quantity, array(array($length, $width, $height, false)));
								}
								else {
									$allShippableItemsHaveDimensionAndWeight = false;
								}
							}
						}
						// collect containers and postage spec for each service class
						$containers_by_service_class = array();
						$cost_by_service_class = array();
						foreach ($service_classes_enabled as $i) {
							$rates_rule = trim($this->config->get('weight3d_' . $geo_zone_id . '_' . $i . '_rate'));
							if ($rates_rule == "")
								break;
							$cost_by_service_class[$i] = -1;
							$rates = array();
							foreach (explode(',', $rates_rule) as $rate) {
								list($w, $c) = explode(':', $rate);
								if (isset($w) && isset($c))
									$rates[trim($w)] = trim($c);
							}
							// calculate postage by weight for baseline comparison
							$weight = $this->cart->getWeight();
							foreach ($rates as $w=>$c) {
								if ($w >= $weight) {
									$cost_by_service_class[$i] = max($cost_by_service_class[$i], floatval($c));
									break;
								}
							}
							// prepare containers for calculator
							$containers = array();
							for ($k=0; $k<MAX_SHIPPING_CONTAINERS; $k++) {
								$handling = trim($this->config->get('weight3d_handling_' . $geo_zone_id . '_' . $i . '_' . $k));
								$length = trim($this->config->get('weight3d_length_' . $geo_zone_id . '_' . $i . '_' . $k));
								$width = trim($this->config->get('weight3d_width_' . $geo_zone_id . '_' . $i . '_' . $k));
								$height = trim($this->config->get('weight3d_height_' . $geo_zone_id . '_' . $i . '_' . $k));
								$weight = trim($this->config->get('weight3d_weight_' . $geo_zone_id . '_' . $i . '_' . $k));
								// if handling fee, dimension and weight are defined
								if (is_numeric($handling) && is_numeric($length) && is_numeric($width) && is_numeric($height) && is_numeric($weight)) {
									// add handling fee to rate
									$costs_adjusted = array();
									foreach (array_values($rates) as $cost) {
										$costs_adjusted[] = floatval($cost) + floatval($handling);
									}
									$container_name = "Container $k";
									$containers[$container_name] = array($container_name, $weight, $length, $width, $height, 0.0, array_keys($rates), $costs_adjusted);
								}
							}
							if (count($containers) > 0)
								$containers_by_service_class[$i] = $containers;
						}
						if (count($items) > 0 && count($containers_by_service_class) > 0) {
							try {
								$api_key = $this->config->get('weight3d_api_key');
								
								if(!empty($api_key)){
									list($container_names_by_service_class, $postage_by_service_class, $dry_weight_by_service_class) = $this->getPostage($api_key, $containers_by_service_class, $items);
								} else {
									list($container_names_by_service_class, $postage_by_service_class, $dry_weight_by_service_class) = $this->getPostageLocal($containers_by_service_class, $items);
								}
									
								// return the maximum of weight based and dim+weight based postage
								foreach ($cost_by_service_class as $service_class=>$postage) {
									// if items do not fit into any of the boxes for this service class
									if ($allShippableItemsHaveDimensionAndWeight && !array_key_exists($service_class, $postage_by_service_class)) {
										$cost_by_service_class[$service_class] = -1;
									}
									else {
										$postage = $postage_by_service_class[$service_class];
										$cost_by_service_class[$service_class] = max($cost_by_service_class[$service_class], floatval($postage));
									}
								}
								
							} catch (Exception $e) {}
						}
						foreach ($cost_by_service_class as $service_class=>$cost) {
							if ($cost != -1) {
								$service_name = $this->config->get("weight3d_name_" . $geo_zone_id . "_$service_class");
								// get number of packaged containers
								$total_parcels = 1;
								if (isset($container_names_by_service_class[$service_class])) {
									$total_parcels = count($container_names_by_service_class[$service_class]);
								}
								// get total net weight
								$total_net_weight = $this->cart->getWeight();
								if (isset($dry_weight_by_service_class[$service_class])) {
									$total_net_weight += $dry_weight_by_service_class[$service_class];
								}
								$parcel_label = $this->language->get('text_parcel');
								if ($total_parcels > 1) {
									$parcel_label = $this->language->get('text_parcels');
								}
								$quote_data['weight3d_' . $geo_zone_id . "_$service_class"] = array(
									'id'           => 'weight3d.weight3d_' . $geo_zone_id . "_$service_class",
									'title'        => $service_name. ' (' . $total_parcels . ' ' . $parcel_label . ', ' . $this->language->get('text_weight') . ' ' . $this->weight->format($total_net_weight, $this->config->get('config_weight_class')) . ')',
									'cost'         => $cost,
									'tax_class_id' => $this->config->get('weight3d_tax_class_id'),
									'text'         => $this->currency->format($this->tax->calculate($cost, $this->config->get('weight3d_tax_class_id'), $this->config->get('config_tax')))
								);	
							}
						}
					}
				}
			}
		}
		$method_data = array();
		if ($quote_data) {
			$method_data = array(
				'id'         => 'weight3d',
				'title'      => $this->language->get('text_title'),
				'quote'      => $quote_data,
				'sort_order' => $this->config->get('weight3d_sort_order'),
				'error'      => FALSE
			);
		}
		return $method_data;
	}

	function getPostageLocal($containers_by_service_class, $items) {
		
		$container_names_by_service_class = array();
		$postage_by_service_class = array();
		$dry_weight_by_service_class = array();
		
		$service_classes = array_keys($containers_by_service_class);
		for ($i=0; $i<count($service_classes); $i++) {
			$service_class = $service_classes[$i];
			$containers = $containers_by_service_class[$i];
			
			
			$container_names = array();//nazwy itemów
			$dry_weight = 0;
			{//TODO paczkoweanie przy wysylce
			
			$container_names[] = $container_name;
			$dry_weight += $containers[$container_name][1];//waga itema
			}
			
			$container_names_by_service_class[$service_class] = $container_names;
			$postage_by_service_class[$service_class] = 0;//cena
			$dry_weight_by_service_class[$service_class] = $dry_weight;
			
		}
		
		return array($container_names_by_service_class, $postage_by_service_class, $dry_weight_by_service_class);
	}
	
	
	function getPostage($api_key, $containers_by_service_class, $items) {
		$items = json_encode($items);
		// handle array
		$hArr = array();
		$service_classes = array_keys($containers_by_service_class);
		for ($i=0; $i<count($service_classes); $i++) {
			$key = $service_classes[$i];
			$containers = json_encode(array_values($containers_by_service_class[$key]));
			$h = curl_init();
			curl_setopt($h, CURLOPT_URL, "http://api.solvingmaze.com");
			curl_setopt($h, CURLOPT_HEADER, 0);
			// when timed out, return value from curl_multi_getcontent() is blank
			curl_setopt($h, 156 , 15000);//CURLOPT_CONNECTTIMEOUT_MS
			curl_setopt($h, 155, 15000);//CURLOPT_TIMEOUT_MS
			curl_setopt($h, CURLOPT_RETURNTRANSFER, 1);
			curl_setopt($h, CURLOPT_POST, true);
			curl_setopt($h, CURLOPT_POSTFIELDS, "containers=$containers&items=$items&key=$api_key");
			array_push($hArr, $h);
		}
		$mh = curl_multi_init();
		foreach ($hArr as $k=>$h)
			curl_multi_add_handle($mh, $h);
		$running = null;
		do {
			curl_multi_exec($mh, $running);
			usleep(300000); // 0.3 second
		} while ($running > 0);
		// get solution
		$container_names_by_service_class = array();
		$postage_by_service_class = array();
		$dry_weight_by_service_class = array();
		foreach ($hArr as $i=>$h) {
			$service_class = $service_classes[$i];
			$containers = $containers_by_service_class[$i];
			$ret_val = curl_multi_getcontent($h);
			if (trim($ret_val) == "") { // time out
				throw new Exception("SolvingMaze API connection time-out.");
			}
			elseif ($ret_val == "[0]") { // no fit
			}
			elseif (strpos($ret_val, "[1,") === 0) { // fit
				$ret_val = json_decode($ret_val, true);
				$container_names = array();
				$dry_weight = 0;
				foreach ($ret_val[2] as $packing) {
					$container_name = $packing[0];
					$container_names[] = $container_name;
					$dry_weight += $containers[$container_name][1];
				}
				$container_names_by_service_class[$service_class] = $container_names;
				$postage_by_service_class[$service_class] = $ret_val[1];
				$dry_weight_by_service_class[$service_class] = $dry_weight;
			}
			else { // calculator returned an error
				throw new Exception("SolvingMaze API internal error.");
			}
		}
		// close all the connections
		foreach ($hArr as $k=>$h)
			curl_multi_remove_handle($mh, $h);
		curl_multi_close($mh);
		return array($container_names_by_service_class, $postage_by_service_class, $dry_weight_by_service_class);
	}
}
?>
